﻿using System;
using System.Diagnostics.CodeAnalysis;
using System.Threading;
using Pfz.Threading;
using Pfz.Threading.Disposers;

namespace Pfz.Extensions
{
	/// <summary>
	/// Adds methods to use ReaderWriterLockSlim easily,
	/// always with time-outs to avoid dead-locks.
	/// See PfzLockConfiguration class if you want to log dead-locks.
	/// </summary>
	public static class PfzReaderWriterLockExtensions
	{
		#region IDisposable locks
			#region ReadLock
				/// <summary>
				/// Abort-unsafe version of read-lock.
				/// Must be disposed to release the lock.
				/// </summary>
				[SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope")]
				public static ReadLockDisposer ReadLock(this ReaderWriterLockSlim readerWriterLock)
				{
					if (readerWriterLock == null)
						throw new ArgumentNullException("readerWriterLock");

					ReadLockDisposer result = new ReadLockDisposer(readerWriterLock, Timeout.Infinite);
					return result;
				}
			
				/// <summary>
				/// Tries to acquire a read-lock on the given object using the default timeout. 
				/// If it fails, returns null.
				/// </summary>
				/// <param name="readerWriterLock">The object to lock.</param>
				/// <returns>A disposable object to release the lock, or null.</returns>
				public static ReadLockDisposer TryReadLock(this ReaderWriterLockSlim readerWriterLock)
				{
					return TryReadLock(readerWriterLock, LockConfiguration.DefaultLockTimeout);
				}
				
				/// <summary>
				/// Tries to acquire a read-lock on the given object using the specified timeout. 
				/// If it fails, returns null.
				/// </summary>
				/// <param name="readerWriterLock">The object to lock.</param>
				/// <param name="timeoutInMilliseconds">The timeout to try for the lock.</param>
				/// <returns>A disposable object to release the lock, or null.</returns>
				[SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope")]
				public static ReadLockDisposer TryReadLock(this ReaderWriterLockSlim readerWriterLock, int timeoutInMilliseconds)
				{
					if (readerWriterLock == null)
						throw new ArgumentNullException("readerWriterLock");

					ReadLockDisposer result = new ReadLockDisposer(readerWriterLock, timeoutInMilliseconds);
					if (result._readerWriterLock != null)
						return result;
					
					return null;
				}
			#endregion
			#region UpgradeableLock
				/// <summary>
				/// Abort-unsafe version of upgradeable-lock.
				/// Must be disposed to release the lock.
				/// </summary>
				[SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope")]
				public static UpgradeableLockDisposer UpgradeableLock(this ReaderWriterLockSlim readerWriterLock)
				{
					if (readerWriterLock == null)
						throw new ArgumentNullException("readerWriterLock");

					UpgradeableLockDisposer result = new UpgradeableLockDisposer(readerWriterLock, -1);
					return result;
				}

				/// <summary>
				/// Tries to acquire an upgradeable lock on the given object, using the default timeout.
				/// If it fails, returns null.
				/// </summary>
				/// <param name="readerWriterLock">The object to try to lock.</param>
				/// <returns>An disposable object to release the lock, or null if the locks fails.</returns>
				public static UpgradeableLockDisposer TryUpgradeableLock(this ReaderWriterLockSlim readerWriterLock)
				{
					return TryUpgradeableLock(readerWriterLock, LockConfiguration.DefaultLockTimeout);
				}
				
				/// <summary>
				/// Tries to acquire an upgradeable lock on the given object, using the specified timeout.
				/// If it fails, returns null.
				/// </summary>
				/// <param name="readerWriterLock">The object to try to lock.</param>
				/// <param name="timeoutInMilliseconds">The maximum time to wait for the lock.</param>
				/// <returns>An disposable object to release the lock, or null if the locks fails.</returns>
				[SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope")]
				public static UpgradeableLockDisposer TryUpgradeableLock(this ReaderWriterLockSlim readerWriterLock, int timeoutInMilliseconds)
				{
					if (readerWriterLock == null)
						throw new ArgumentNullException("readerWriterLock");

					UpgradeableLockDisposer result = new UpgradeableLockDisposer(readerWriterLock, timeoutInMilliseconds);
					if (result._readerWriterLock != null)
						return result;
					
					return null;
				}
			#endregion
			#region WriteLock
				/// <summary>
				/// Abort-unsafe version of write-lock.
				/// Must be disposed to release the lock.
				/// </summary>
				[SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope")]
				public static WriteLockDisposer WriteLock(this ReaderWriterLockSlim readerWriterLock)
				{
					if (readerWriterLock == null)
						throw new ArgumentNullException("readerWriterLock");

					WriteLockDisposer result = new WriteLockDisposer(readerWriterLock, -1);
					return result;
				}

				/// <summary>
				/// Tries to acquire a write-lock on the given object using the default timeout.
				/// If it fails, returns null.
				/// </summary>
				/// <param name="readerWriterLock">The object to lock.</param>
				/// <returns>A disposable object to release the lock, or null.</returns>
				public static WriteLockDisposer TryWriteLock(this ReaderWriterLockSlim readerWriterLock)
				{
					return TryWriteLock(readerWriterLock, LockConfiguration.DefaultLockTimeout);
				}
				
				/// <summary>
				/// Tries to acquire a write-lock on the given object using the specified timeout.
				/// If it fails, returns null.
				/// </summary>
				/// <param name="readerWriterLock">The object to lock.</param>
				/// <param name="timeoutInMilliseconds">The maximum time to wait for the lock.</param>
				/// <returns>A disposable object to release the lock, or null.</returns>
				[SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope")]
				public static WriteLockDisposer TryWriteLock(this ReaderWriterLockSlim readerWriterLock, int timeoutInMilliseconds)
				{
					if (readerWriterLock == null)
						throw new ArgumentNullException("readerWriterLock");

					WriteLockDisposer result = new WriteLockDisposer(readerWriterLock, timeoutInMilliseconds);
					if (result._readerWriterLock != null)
						return result;
					
					return null;
				}
			#endregion
		#endregion
	}
}
